class tShapeContainer
{
  // shape [0] is drawn first, shape [shapes.length-1] is drawn last
  public tShape shapes[];
  // -1 when no shape is active
  int activeShape;
  
  /**
   * Creates the seven basic shapes
   */
  void initStandardShapes()
  {
    int gs = globalGridSize;
    int boxSize = 350;
    color baseColor = color(255,255,255);
    int midx = screenSize[0]/2-boxSize/2;
    int midy = screenSize[1]/2-boxSize/2;
    int areaOfShape = 0;
    shapes = null;
    
    switch (difficulty)
    {
      case 0:
        baseColor = color(50,200,50);
        break;
      case 1:
        baseColor = color(220,220,50);
        break;
      case 2:
        baseColor = color(200,50,50);
        break;
    }
    
    int coords0[][] = { {0,0}, {175,0}, {0,175} };
    int pos0[] = {0,0};
    int coords1[][] = { {175,0}, {262,87}, {174,175}, {87,88} };
    int pos1[] = {175,0};
    int coords2[][] = { {175,0}, {349,0}, {262,87} };
    int pos2[] = {175,0};
    int coords3[][] = { {87,88}, {174,175}, {87,262} };
    int pos3[] = {87,88};
    int coords4[][] = { {0,175}, {87,88}, {87,262}, {0,349} };
    int pos4[] = {0,175};
    int coords5[][] = { {0,349}, {174,175}, {349,349} };
    int pos5[] = {0,349};
    int coords6[][] = { {174,175}, {349,0}, {349,349} };
    int pos6[] = {175,174};
    
    for (int i = 0; i < 7; i++)
    {
      int coords[][] = null;
      int pos[] = null;
      switch (i)
      {
        case 0:
          coords = coords0;
          pos = pos0;
          areaOfShape = 2;
          break;
        case 1:
          coords = coords1;
          pos = pos1;
          areaOfShape = 2;
          break;
        case 2:
          coords = coords2;
          pos = pos2;
          areaOfShape = 1;
          break;
        case 3:
          coords = coords3;
          pos = pos3;
          areaOfShape = 1;
          break;
        case 4:
          coords = coords4;
          pos = pos4;
          areaOfShape = 2;
          break;
        case 5:
          coords = coords5;
          pos = pos5;
          areaOfShape = 4;
          break;
        case 6:
          coords = coords6;
          pos = pos6;
          areaOfShape = 4;
          break;
      }
      int posOffset[] = {pos[0] % gs, pos[1] % gs};
      for (int j = 0; j < coords.length; j++)
      {
        coords[j][0] += posOffset[0] - pos[0];
        coords[j][1] += posOffset[1] - pos[1];
      }
      pos[0] -= posOffset[0];
      pos[1] -= posOffset[1];
      this.addShape(coords, baseColor);
      pos[0] += midx - (midx%gs);
      pos[1] += midy - (midy%gs);
      shapes[0].setPosition(pos);
      shapes[0].areaOfShape = areaOfShape;
    }
  }
  
  /**
   * Adds a shape to the begining of the shapes[] array
   * @see addShape(tShape)
   * @see tShape(inCoords, baseColor)
   */
  void addShape(int[][] coords, color baseColor)
  {
    tShape myShape = new tShape(coords, baseColor);
    this.addShape(myShape);
  }
  
  /**
   * @param newShape the shape to be added
   * @see addShape(coords, baseColor)
   */
  void addShape(tShape newShape)
  {
    if (shapes != null && shapes.length > 0)
    {
      tShape oldShapes[] = shapes;
      shapes = new tShape[shapes.length+1];
      for (int i = 0; i < oldShapes.length; i++)
      {
        shapes[i+1] = oldShapes[i];
      }
    }
    else
    {
      shapes = new tShape[1];
    }
    shapes[0] = newShape;
  }
  
  /**
   * Draws every shape, starting with shapes[0]
   * If any shape returns false from it's draw function, stop and return false
   * @return true if all draws succeeded, false otherwise
   */
  boolean doDraw()
  {
    if (shapes != null)
    {
      for (int i = 0; i < shapes.length; i++)
      {
        boolean retval = shapes[i].doDraw();
        if (!retval)
        {
          return false;
        }
      }
    }
    
    return true;
  }
  
  /**
   * calls the mouseUpdate function of all shapes and
   * sets the "activeShape" value
   * @param mousePos The position of the mouse
   * @see getMouseDownLeft()
   * @see isMouseDown
   */
  void update(int mousePos[])
  {
    // find the active shape
    if (mouseDown[0] == true)
    {
      if (activeShape == -1 && mouseJustPressed[0] == true && shapes != null)
      {
        for (int i = shapes.length-1; i > -1; i--)
        {
          if (shapes[i].getPointCollision(mousePos))
          {
            this.bringToEnd(i);
            activeShape = shapes.length-1;
            break;
          }
        }
      }
    }
    else
    {
      activeShape = -1;
    }
    
    if (activeShape > -1)
    {
      int spaceKey = 32;
      if (keyJustPressed[spaceKey] == true)
      {
        shapes[activeShape].rotateClockwise();
      }
    }
    
    // call the update function
    if (shapes != null)
    {
      for (int i = 0; i < shapes.length; i++)
      {
        if (i == activeShape)
        {
          shapes[i].update(mousePos, true);
        }
        else
        {
          shapes[i].update(mousePos, false);
        }
      }
    }
  }
  
  /**
   * Brings the shape at the given index to the end of the list,
   * shifting all shapes in front of it down
   * @shapeIndex the shape to move to the end of the list
   */
  void bringToEnd(int shapeIndex)
  {
    if (shapes.length < 2)
    {
      return;
    }
    
    tShape myShape = shapes[shapeIndex];
    for (int i = shapeIndex; i < shapes.length-1; i++)
    {
      shapes[i] = shapes[i+1];
    }
    shapes[shapes.length-1] = myShape;
  }
  
  /**
   * Gets a colon-separated-value list of element IDs from the children shapes
   * @includeNegatives include the default -1 element ID's in the list?
   * @return a list in the form:
   *  0:-1:3:etc
   */
  String getElementIDs(boolean includeNegatives)
  {
    int IDs[] = new int[100];
    int IDi = 0;
    
    if (shapes == null)
    {
      return "";
    }
    
    for (int i = 0; i < shapes.length; i++)
    {
      int id = shapes[i].elementID;
      if (id < 0 && !includeNegatives)
      {
        continue;
      }
      IDs[IDi] = id;
      IDi++;
    }
    
    String retval = "";
    boolean isFirst = true;
    for (int i = 0; i < 100; i++)
    {
      for (int j = 0; j < IDi; j++)
      {
        if (IDs[j] == i)
        {
          if (!isFirst)
          {
            retval += ":";
          }
          retval += i;
          isFirst = false;
        }
      }
    }
    return retval;
  }
  
  /**
   * Returns a list of all unique shape global coordinates,
   * up to the first 1000 coordinates
   */
  int[][] getListShapeCoords()
  {
    int retval[][] = new int[1000][2];
    int ri = 0; // retval index, equal to retval length -1
    
    if (shapes == null)
    {
      return null;
    }
    
    for (int i = 0; i < shapes.length; i++)
    {
      for (int j = 0; j < shapes[i].coords.length; j++)
      {
        int x = shapes[i].coords[j][0]+shapes[i].position[0];
        int y = shapes[i].coords[j][1]+shapes[i].position[1];
        boolean alreadyContainsCoordinate = false;
        for (int k = 0; k < ri; k++)
        {
          if (retval[k][0] == x &&
              retval[k][1] == y)
          {
            alreadyContainsCoordinate = true;
            break;
          }
        }
        if (alreadyContainsCoordinate == false)
        {
          retval[ri][0] = x;
          retval[ri][1] = y;
          ri++;
          if (ri >= 1000)
          {
            i = shapes.length;
            break;
          }
        }
      }
    }
    
    // create the new retval for the new length
    int newRetval[][] = new int[ri][2];
    for (int i = 0; i < newRetval.length; i++)
    {
      newRetval[i][0] = retval[i][0];
      newRetval[i][1] = retval[i][1];
    }
    
    return newRetval;
  }
  
  /**
   * Attempts to "snap" the given shape into place with
   *   A: the level, if it has level data and succeeds
   *   B: the other shapes, if the level data fails
   * @param tShape the shape to snap into place with the other shapes
   * @return true if snapping works, false otherwise
   */
  boolean snap(tShape inShape, int maxDistance)
  {
    int allShapeCoords[][] = this.getListShapeCoords();
    int listShapeCoords[][] = new int[allShapeCoords.length - inShape.coords.length][2];
    int globalShapeCoords[][] = inShape.getGlobalCoords();
    
    // get the global coordinates of all shapes, minus this shape
    int listShapeCoordsi = 0;
    for (int i = 0; i < allShapeCoords.length; i++)
    {
      boolean uniqueCoordinate = true;
      for (int j = 0; j < globalShapeCoords.length; j++)
      {
        if (allShapeCoords[i][0] == globalShapeCoords[j][0] &&
            allShapeCoords[i][1] == globalShapeCoords[j][1])
        {
          uniqueCoordinate = false;
          break;
        }
      }
      if (uniqueCoordinate == true)
      {
        listShapeCoords[listShapeCoordsi][0] = allShapeCoords[i][0];
        listShapeCoords[listShapeCoordsi][1] = allShapeCoords[i][1];
        listShapeCoordsi++;
      }
    }
    
    // try to snap, determine if it snapped
    if (listShapeCoords != null)
    {
      if (inShape.snapToCoords(listShapeCoords, maxDistance))
      {
        return true;
      }
    }
    
    return false;
  }
}
